import {BaseClient, Config, Resp} from 'http-util.js';

export const gmStore = new class{
	clear(){
		this.keys().forEach(key=>this.delete(key));
	}
	
	assign(obj){
		for (const [k, v] of Object.entries(obj)){
			this.set(k, v);
		}
	}
	
	toObject(){
		return Object.fromEntries(this.keys().map(key=>[key, this.get(key)]));
	}
	
	// 获取key对应的值，若不存在，则设置成value并返回,value可以是一个值或者生成值的函数
	getOrSet(key, value){
		if (typeof value==='function'){
			value = value();
		}
		if (!this.has(key)){
			this.set(key, value);
		}
		return this.get(key);
	}
	
	// 声明为async函数是为了支持异步获取值
	async getOrSetAsync(key, value){
		if (typeof value==='function'){
			value = await value();
		}
		if (!this.has(key)){
			this.set(key, value);
		}
		return this.get(key);
	}
	
	// ================ 基本方法 ================
	keys(){ // 返回所有存储的键
		return GM_listValues();
	}
	has(k){
		return this.keys().includes(k);
	}
	get(k){
		return GM_getValue(k);
	}
	set(k, v){
		GM_setValue(k, v);
	}
	delete(k){
		GM_deleteValue(k);
	}
	addListener(k, callback){
		GM_addValueChangeListener(k, callback);
	}
	removeListener(id){ // 停止对变量的监听
		GM_removeValueChangeListener(id);
	}
};

function createResp(config, xhr){
	return new Resp({
		config,
		rawResp:xhr,
		url:xhr.finalUrl,
		status:xhr.status,
		statusText:xhr.statusText,
		headers:new Headers(
				xhr.responseHeaders.trim()
					 .split(/[\r\n]+/)
					 .filter(x=>x) // 不为空串
					 .map(x=>x.split(': ', 2)),
		),
	});
}

// 创建 GM_xmlhttpRequest 的参数对象
function createDetails(config){
	const {data, onUploading, onDownloading, rawOptions} = config;
	const href = config.getHref();
	const method = config.getMethod();
	const headers = config.getHeaders();
	
	// 构建请求参数，注意Violentmonkey默认发送cookie，即使是跨域请求
	const details = { // GM.xmlHttpRequest的参数details
		url:href,
		method,
		headers:Object.fromEntries(headers.entries()), // headers仅支持对象类型
		data:data,
		upload:{}, // 目前暂不支持上传进度监控
	};
	Object.assign(details, rawOptions); // 其他请求选项
	details.responseType = 'arraybuffer'; // 接收arraybuffer，用于转换成流
	
	// 监控下载和上传进度
	if (onUploading){
		details.upload.onprogress = (e)=>{
			onUploading(e.loaded, e.lengthComputable? e.total: null);
		};
	}
	if (onDownloading){
		details.onprogress = (e)=>{
			onDownloading(e.loaded, e.lengthComputable? e.total: null);
		};
	}
	
	return details;
}

export class GMClient extends BaseClient{
	constructor(config){
		super(config); // 调用父类的构造函数
	}
	
	_send({checkStatus = false, signal} = {}){
		const config = this.config.clone();
		
		const configPromise = (async()=>{
			const {onBeforeSend} = config;
			if (onBeforeSend){
				await onBeforeSend(config);
			}
			return createDetails(config);
		})();
		
		const {promise, resolve, reject} = Promise.withResolvers(); // 收到响应头时的Promise
		
		(async()=>{
			
			const details = await configPromise; // 等待请求参数对象
			
			let abortFn = null; // 中断函数
			let controller;
			const stream = new ReadableStream({
				start(ctrl){
					controller = ctrl;
				},
			});
			
			// 响应头收到
			details.onreadystatechange = async(xhr)=>{
				if (xhr.readyState===XMLHttpRequest.HEADERS_RECEIVED){
					const status = xhr.status; // 响应状态码
					if (checkStatus && !(200<=status && status<400)){ // 检查状态码是否在[200,400)范围内
						abortFn(); // 中断请求
						reject(this._newStatusError(status));
						return;
					}
					const resp = createResp(config, xhr);
					resp.bodyStream = stream;
					
					// 设置好bodyStream之后，调用 onReceive回调函数
					if (config.onReceive){
						await config.onReceive(resp); // 传入响应对象和配置
					}
					resolve(resp);
				}
			};
			details.onload = async(xhr)=>{
				controller.enqueue(new Uint8Array(xhr.response));
				controller.close();
			};
			details.onerror = ()=>reject(new Error('error')); // 发生网络层面的错误
			details.onabort = ()=>reject(new Error('abort')); // 请求被中止
			details.ontimeout = ()=>reject(new Error('timeout')); // 请求超时
			
			// 发送请求，并保存中断函数
			abortFn = GM_xmlhttpRequest(details).abort;
			
			// 检查是否中断
			if (signal){
				if (signal.aborted){
					abortFn(); // 中断请求
				} else {
					signal.addEventListener('abort', ()=>{
						abortFn(); // 中断请求
					});
				}
			}
		})();
		
		return promise;
	}
}

// 简化使用的对外的接口
export function reqGM(url, options){
	// 创建Client并返回
	return new GMClient(new Config({url, ...options}));
}
